#include "GLState.h"
#include <GL/glew.h>
#include <memory>
#include <set>
namespace Umikaze::Core {

class GlTextures {

public:
  uint32_t genTexture() {
    uint32_t textureId;
    glGenTextures(1, &textureId);
    return textureId;
  }

  void resetLocation() { _currentLocation = 0; }

  uint32_t getLocation() {
    // TODO:用glCapabilities来替换16
    if (_currentLocation >= 16) {
      std::cerr << "超出了texture数量上限" << std::endl;
    }
    return _currentLocation++;
  }

  void setTexture2D(uint32_t textureId, uint32_t location) {
    auto &state = GlState::instance();
    state->activeTexture(location);
    state->bindTexture(textureId, GL_TEXTURE_2D);
  }

  void setTextureCubeMap(uint32_t textureId, uint32_t location) {
    auto &state = GlState::instance();
    state->activeTexture(location);
    state->bindTexture(textureId, GL_TEXTURE_CUBE_MAP);
  }

  void setTexture2DArray(uint32_t textureId, uint32_t location) {
    auto &state = GlState::instance();
    state->activeTexture(location);
    state->bindTexture(textureId, GL_TEXTURE_2D_ARRAY);
  }

  void updateTexture2D(uint32_t textureId, void *data, int width, int height,
                       GLenum internalFormat, GLenum format, GLenum size) {
    auto &state = GlState::instance();
    state->bindTexture(textureId, GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
    GLfloat borderColor[] = {1.0, 1.0, 1.0, 1.0};
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format,
                 size, data);

    glGenerateMipmap(GL_TEXTURE_2D);
  }

  void updateTexture2DArray(uint32_t textureId, void *data, int width,
                            int height, int depth, GLenum internalFormat,
                            GLenum format) {
    auto &state = GlState::instance();
    state->bindTexture(textureId, GL_TEXTURE_2D_ARRAY);

    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);

    constexpr float bordercolor[] = {1.0f, 1.0f, 1.0f, 1.0f};
    glTexParameterfv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, bordercolor);

    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, internalFormat, width, height, depth,
                 0, format, GL_UNSIGNED_BYTE, data);
  }

  void updateTextureCubeMap(uint32_t textureId, void **data, int width,
                            int height, GLenum format) {
    auto &state = GlState::instance();
    state->bindTexture(textureId, GL_TEXTURE_CUBE_MAP);

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    for (uint32_t i = 0; i < 6; ++i) {
      glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height,
                   0, format, GL_UNSIGNED_BYTE, data[i]);
    }
  }

  void setDepthAttachment(uint32_t textureId) {
    auto &state = GlState::instance();
    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textureId, 0);
  }

  void setDepthStencilAttachment(uint32_t textureId) {
    auto &state = GlState::instance();
    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, textureId,
                         0);
  }

  void setTextureAttachment(uint32_t textureId, uint32_t location) {
    auto &state = GlState::instance();
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + location,
                         textureId, 0);
  }

  void deleteTexture(uint32_t textureId) { glDeleteTextures(1, &textureId); }

  static const std::unique_ptr<GlTextures> &instance() {
    if (!_instance) {
      _instance = std::unique_ptr<GlTextures>(new GlTextures());
    }
    return _instance;
  }

private:
  GlTextures() = default;

  std::unordered_map<uint32_t, GLenum> _textureTypes;

  static std::unique_ptr<GlTextures> _instance;

private:
  uint32_t _currentLocation = 0;
};

} // namespace Umikaze::Core